﻿using System;
using UnityEngine;

namespace Core.Touch
{
    public class TouchScreen
    {
        public event Action OnClick;
        public event Action<EDir4> OnBeginDrag;
        public event Action<EDir4> OnEndDrag;
        public event Action OnEndMooring;
        public event Action OnEndReturn;

        private const float SPEED = 10f;
        public const float REACTION_DISTANCE = 10f;

        private Camera camera;
        private Transform transform;

        private Vector3 downPos;
        private Vector3 currPos;
        private Vector3 lastPos;
        private bool isDrag;
        private bool isDown;
        private bool isVertical = true;

        private Vector3 delta;
        private Vector3 startPos;
        private EDir4 startDir;
        private Vector3 targetPos;

        private Action funcPointer;

        public Vector3 Position
        {
            get => transform.localPosition;
            set => transform.localPosition = value;
        }

        public void Awake(Camera camera)
        {
            this.camera = camera;
            transform = camera.transform;

            isDown = false;
            isDrag = false;

            isVertical = true;

            Debug.Log($"Screen.size=({Screen.width},{Screen.height})");
        }

        public void Dispose()
        {
            camera = null;
            transform = null;

            isDown = false;
            isDrag = false;

            isVertical = true;
        }

        public void Update()
        {
            if (funcPointer != null)
            {
                funcPointer?.Invoke();
                return;
            }

            #region KeyBoard

            if (Input.GetKeyUp(KeyCode.DownArrow))
            {
                startDir = EDir4.Up;
                OnKeyClickAutoDrag();
                return;
            }

            if (Input.GetKeyUp(KeyCode.UpArrow))
            {
                startDir = EDir4.Down;
                OnKeyClickAutoDrag();
                return;
            }

            if (Input.GetKeyUp(KeyCode.LeftArrow))
            {
                startDir = EDir4.Right;
                OnKeyClickAutoDrag();
                return;
            }

            if (Input.GetKeyUp(KeyCode.RightArrow))
            {
                startDir = EDir4.Left;
                OnKeyClickAutoDrag();
                return;
            }

            #endregion

            #region Touch Screen

            if (isDown == false)
            {
                if (Input.GetMouseButtonDown(0))
                {
                    OnDownHandler();
                }

                return;
            }

            if (Input.GetMouseButtonUp(0))
            {
                OnUpHandler();
                return;
            }

            currPos = Input.mousePosition;
            if (isDrag == false)
            {
                if (Vector3.Distance(currPos, downPos) > REACTION_DISTANCE)
                {
                    OnBeginDragHandler();
                }
            }
            else
            {
                if (currPos != lastPos)
                {
                    OnDragHandler();
                }
            }

            #endregion
        }

        private void OnDownHandler()
        {
            if (Utils.Touch.PointOverGui(Input.mousePosition))
            {
                return;
            }

            downPos = Input.mousePosition;
            currPos = downPos;
            lastPos = downPos;
            isDown = true;
        }

        private void OnUpHandler()
        {
            if (!isDown)
            {
                return;
            }

            if (isDrag == false)
            {
                OnClickHandler();
            }
            else
            {
                OnEndDragHandler();
            }

            isDrag = false;
            isDown = false;
        }

        private float ClampY(float y)
        {
            return startDir switch
            {
                EDir4.Up => Mathf.Clamp(y, -10, 0),
                EDir4.Down => Mathf.Clamp(y, 0, 10),
                _ => y
            };
        }

        private float ClampX(float x)
        {
            switch (startDir)
            {
                case EDir4.Right:
                    x = Mathf.Clamp(x, -5.63f, 0);
                    break;
                case EDir4.Left:
                    x = Mathf.Clamp(x, 0, 5.63f);
                    break;
            }

            return x;
        }

        private Vector3 GetTargetPos(Vector3 start_pos)
        {
            var pos = start_pos;
            switch (startDir)
            {
                case EDir4.Up:
                    pos += new Vector3(0, -10, 0);
                    break;
                case EDir4.Down:
                    pos += new Vector3(0, 10, 0);
                    break;
                case EDir4.Left:
                    pos += new Vector3(5.63f, 0, 0);
                    break;
                case EDir4.Right:
                    pos += new Vector3(-5.63f, 0, 0);
                    break;
            }

            return pos;
        }

        private void OnBeginDragHandler()
        {
            var dir = currPos - downPos;
            var angle = Utils.Math.GetAngle(dir, new Vector3(0, 1, 0));
            //var angle = Vector2.SignedAngle(dir, new Vector3(0, 1, 0));
            startDir = Utils.Dir.Get4(angle);

            Debug.Log($"m_start_dir={startDir} angle={angle}");
            if (startDir == EDir4.Invalid)
            {
                return;
            }

            //Debug.Log($"OnBeginDrag down={m_down_pos}; curr={m_curr_pos};angle={angle}; last={m_last_pos}");
            OnBeginDrag?.Invoke(startDir);

            isDrag = true;

            isVertical = startDir == EDir4.Up || startDir == EDir4.Down;

            startPos = Position;
            targetPos = GetTargetPos(startPos);

            OnDragHandler();
        }

        private void OnDragHandler()
        {
            //Debug.Log($"OnDrag down={m_down_pos}; curr={m_curr_pos}; last={m_last_pos}");

            Vector3 pos1 = camera.ScreenToWorldPoint(lastPos); //转换至世界坐标
            Vector3 pos2 = camera.ScreenToWorldPoint(currPos);

            lastPos = currPos;

            delta = pos2 - pos1;

            if (isVertical)
            {
                delta.x = 0;

                var y = Position.y;
                y -= delta.y; //向量偏移
                y = ClampY(y);

                Position = new Vector3(Position.x, y, Position.z);
            }
            else
            {
                delta.y = 0;

                var x = Position.x;
                x -= delta.x; //向量偏移
                x = ClampX(x);

                Position = new Vector3(x, Position.y, Position.z);
            }
        }

        private void OnEndDragHandler()
        {
            //Debug.Log("OnEndDrag");
            OnEndDrag?.Invoke(startDir);
            var curr_pos = Position;
            //Debug.Log($"start_pos = {m_start_pos} curr_pos={curr_pos} m_delta={m_delta}");
            if (isVertical)
            {
                var rate = (curr_pos.y - startPos.y) / (targetPos.y - startPos.y);
                if (delta.y > 0.1f)
                {
                    funcPointer = Mooring;
                }
                else if (delta.y < -0.1f)
                {
                    funcPointer = Return;
                }
                else if (rate > 0.5f)
                {
                    funcPointer = Mooring;
                }
                else
                {
                    funcPointer = Return;
                }
            }
            else
            {
                var rate = (curr_pos.x - startPos.x) / (targetPos.x - startPos.x);
                if (delta.x > 0.1f)
                {
                    funcPointer = Mooring;
                }
                else if (delta.x < -0.1f)
                {
                    funcPointer = Return;
                }
                else if (rate > 0.5f)
                {
                    funcPointer = Mooring;
                }
                else
                {
                    funcPointer = Return;
                }
            }
        }

        private void OnClickHandler()
        {
            //Debug.Log("OnClick");
            OnClick?.Invoke();
        }


        private void Mooring()
        {
            if (Vector3.Distance(Position, targetPos) < 1)
            {
                //Debug.Log("Mooring");
                Position = targetPos;
                funcPointer = null;
                OnEndMooring?.Invoke();
                return;
            }

            Position = Vector3.Lerp(Position, targetPos, SPEED * Time.deltaTime);
        }

        private void Return()
        {
            if (Vector3.Distance(Position, startPos) < 1)
            {
                //Debug.Log("Return");
                Position = startPos;
                funcPointer = null;
                OnEndReturn?.Invoke();
                return;
            }

            Position = Vector3.Lerp(Position, startPos, SPEED * Time.deltaTime);
        }

        private void OnKeyClickAutoDrag()
        {
            OnBeginDrag?.Invoke(startDir);
            OnEndDrag?.Invoke(startDir);

            startPos = Position;
            targetPos = GetTargetPos(startPos);

            funcPointer = Mooring;
        }
    }
}
